# 1. 导入所有依赖模块
import os
import io
import re
import base64
import uuid
import json
import time
import logging
import numpy as np
import cv2
import dashscope
import imagehash
from PIL import Image
from urllib3 import disable_warnings
from urllib3.exceptions import InsecureRequestWarning
from dotenv import load_dotenv
from flask import Flask, request, jsonify, render_template, send_from_directory, make_response, redirect, url_for
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy
from moviepy.editor import VideoFileClip
import pandas as pd
from datetime import datetime

# 2. 初始化应用与配置
app = Flask(__name__)
CORS(app)

# 核心配置（支持500MB视频）
app.config['UPLOAD_FOLDER'] = 'uploads'  # 视频临时存储
app.config['FRAME_FOLDER'] = 'function_frames'  # 功能帧存储
app.config['MAX_CONTENT_LENGTH'] = 500 * 1024 * 1024  # 500MB视频上限
app.config['JSON_AS_ASCII'] = False  # 支持中文
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///design_analysis.db'  # 历史记录数据库
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['UPLOAD_TIMEOUT'] = 300  # 5分钟上传超时

# 3. 初始化数据库（存储分析历史）
db = SQLAlchemy(app)


class AnalysisRecord(db.Model):
    id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
    filename = db.Column(db.String(255), nullable=False)
    file_size = db.Column(db.Float, nullable=False)  # 单位：MB
    upload_time = db.Column(db.DateTime, default=datetime.utcnow)
    status = db.Column(db.String(20), default='processing')  # processing/completed/failed
    result_data = db.Column(db.Text, nullable=True)  # 存储JSON格式的分析结果

    def to_dict(self):
        return {
            'id': self.id,
            'filename': self.filename,
            'file_size': round(self.file_size, 2),
            'upload_time': self.upload_time,
            'status': self.status
        }


# 4. 创建必要目录与数据库表
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
os.makedirs(app.config['FRAME_FOLDER'], exist_ok=True)
with app.app_context():
    db.create_all()

# 5. 日志配置（支持中文）
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('design_analysis.log', encoding='utf-8'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

# 6. 配置DashScope API密钥（产品设计思维分析用）
load_dotenv()
DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY")
if not DASHSCOPE_API_KEY:
    logger.error("❌ 未配置DashScope API密钥！请在.env文件添加：DASHSCOPE_API_KEY=你的密钥")
    exit(1)
dashscope.api_key = DASHSCOPE_API_KEY
logger.info("✅ DashScope API密钥配置成功")

# 7. SSL验证处理
ssl_verify = True
if os.getenv('DISABLE_SSL_VERIFY', 'false').lower() == 'true':
    logger.warning("⚠️ SSL验证已禁用（仅测试环境）")
    disable_warnings(InsecureRequestWarning)
    ssl_verify = False


# 8. 工具函数：帧过滤（大视频优化）
def is_function_frame(frame):
    """过滤黑屏、纯背景、重复帧"""
    # 过滤黑屏（亮度<30）
    gray_frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
    avg_brightness = np.mean(gray_frame)
    if avg_brightness < 30:
        return False

    # 过滤纯背景（边缘占比<1%）
    edges = cv2.Canny(gray_frame, 50, 150)
    edge_ratio = np.sum(edges > 0) / (edges.shape[0] * edges.shape[1])
    if edge_ratio < 0.01:
        return False

    # 过滤重复帧（哈希差值<2）
    global last_frame_hash
    current_hash = imagehash.dhash(Image.fromarray(frame), hash_size=8)
    if 'last_frame_hash' not in globals() or last_frame_hash is None:
        last_frame_hash = current_hash
        return True
    if current_hash - last_frame_hash < 2:
        return False
    last_frame_hash = current_hash
    return True


# 9. 优化后的帧提取函数（采样率+过滤条件双调整）
def extract_function_frames(video_path, max_frames=200):
    logger.info(f"提取大视频功能帧: {video_path}")
    clip = VideoFileClip(video_path)
    fps = clip.fps
    frame_interval = max(1, int(fps * 1))  # 改为1秒/帧（原2秒）
    total_frames = int(clip.duration * fps)

    # 新增：允许纯文字界面（如设置界面）
    def is_function_frame(frame):
        gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
        # 放宽黑屏阈值（原<30→<20）
        if np.mean(gray) < 20:
            return False
        # 放宽边缘检测（原<1%→<0.5%）
        edges = cv2.Canny(gray, 30, 100)
        if np.sum(edges > 0) / (gray.size) < 0.005: return False
        # 新增：检测文字区域（允许纯操作界面）
        text_mask = cv2.createTextDetector().detect(gray)[0]
        if text_mask is not None and np.sum(text_mask) > 1000: return True
        # 重复帧检测（哈希差<3，原<2）
        global last_frame_hash
        current_hash = imagehash.dhash(Image.fromarray(frame), 8)
        if last_frame_hash and (current_hash - last_frame_hash) < 3: return False
        last_frame_hash = current_hash
        return True

    # 新增：保留原始尺寸帧（提升OCR准确率）
    function_frames = []
    for t in np.arange(0, clip.duration, 1):  # 按时间戳遍历更准确
        try:
            frame = clip.get_frame(t)
            if not is_function_frame(frame): continue
            # 保留原始尺寸+高质量（提升AI识别率）
            img = Image.fromarray(frame)
            buf = io.BytesIO()
            img.save(buf, format='PNG', quality=90)  # 改用PNG保留文字细节
            function_frames.append({
                "time": round(t, 2),
                "frame_base64": f"data:image/png;base64,{base64.b64encode(buf.getvalue()).decode()}",
                "raw_frame": frame  # 保留原始帧数据用于调试
            })
            if len(function_frames) >= max_frames: break
        except:
            pass
    clip.close()
    return function_frames if len(function_frames) >= 3 else []  # 至少3帧才触发分析

# 10. 核心提示词：产品设计思维分析（聚焦可复用思路）
def generate_design_thinking_prompt(function_frames):
    frame_count = len(function_frames)
    used_frames = min(10, frame_count)
    return f"""# 产品功能点级设计策略深度剖析指令
基于{used_frames}帧视频，从「功能点拆解→设计细节→可复用策略」三级维度分析，输出严格JSON格式（无多余文本）。

## 1. 功能点全景拆解（逐个分析）
对每一个可识别的功能点（如"登录按钮""筛选器""数据看板"），需包含：
- `function_point`: 功能点名称（如"带历史记录的搜索框"）
- `core_purpose`: 核心用途（如"快速召回用户历史查询，减少重复输入"）
- `location`: 位置信息（如"顶部导航栏右侧，搜索图标+输入框组合"）
- `trigger_conditions`: 触发条件（如"点击图标展开/输入字符实时触发"）
- `interaction_details`: 交互细节（如"输入时下拉展示历史记录，点击条目自动填充"）

## 2. 功能点设计策略（可复用维度）
针对每个功能点，提炼其设计策略，需包含：
- `strategy_name`: 策略名称（如"渐进式历史记录展示"）
- `function_point_relation`: 关联功能点（对应上述`function_point`）
- `design_details`: 设计细节（如"仅展示最近5条记录，超过则折叠为'查看更多'"）
- `user_value`: 用户价值（如"降低输入成本，提升操作效率"）
- `technical_implementation`: 技术实现暗示（如"本地存储+防抖处理"）
- `applicable_scenarios`: 可复用场景（如"所有需要用户输入的高频场景"）
- `edge_case_handling`: 边界处理（如"无历史记录时显示'暂无记录'引导文案"）

## 3. 核心学习点（基于功能点的本质提炼）
- `function_design_philosophy`: 功能设计哲学（如"高频操作优先减少认知负荷"）
- `cross_function_patterns`: 跨功能共性模式（如"所有表单提交按钮均采用'主色+图标'强化识别"）
- `tradeoffs`: 设计取舍（如"为了简洁性，隐藏了高级筛选，通过'更多'按钮触发"）

## 输出格式（缺失填"未观察到"）
{{
  "function_points": [
    {{
      "function_point": "",
      "core_purpose": "",
      "location": "",
      "trigger_conditions": "",
      "interaction_details": ""
    }}
  ],
  "function_design_strategies": [
    {{
      "strategy_name": "",
      "function_point_relation": "",
      "design_details": "",
      "user_value": "",
      "technical_implementation": "",
      "applicable_scenarios": "",
      "edge_case_handling": ""
    }}
  ],
  "key_lessons": {{
    "function_design_philosophy": "",
    "cross_function_patterns": "",
    "tradeoffs": ""
  }}
}}
"""


# 11. 核心函数：产品设计思维分析
def analyze_design_thinking(function_frames):
    if not function_frames:
        return {"error": "未提取到有效功能帧"}

    # 构建API请求（单一层级模态）
    content = []
    for idx, frame in enumerate(function_frames[:10]):
        content.append({"text": f"第{idx + 1}帧（{frame['time']}s）：分析产品界面/配置逻辑/设计思维"})
        content.append({"image": frame["frame_base64"]})

    # 加入提示词
    prompt = generate_design_thinking_prompt(function_frames)
    content.append({"text": prompt})

    # 调用API
    try:
        logger.info("调用Qwen-VL-Max分析设计思维")
        response = dashscope.MultiModalConversation.call(
            model='qwen-vl-max',
            messages=[{"role": "user", "content": content}],
            timeout=300
        )

        if response.status_code != 200:
            return {"error": f"API调用失败: {response.message}"}

        # 提取结果
        analysis_text = ""
        for item in response.output.choices[0].message.content:
            if isinstance(item, dict) and "text" in item:
                analysis_text += item["text"].strip()

        # 清理并解析JSON
        clean_text = analysis_text.replace("```json", "").replace("```", "").strip()
        clean_text = re.sub(r',\s*([}\]])', r'\1', clean_text)  # 修复尾部逗号
        analysis_result = json.loads(clean_text)

        # 补充默认值
        # 补充默认值示例（仅展示新增部分）
        analysis_result.setdefault("function_points", [
            {
                "function_point": "未识别到具体功能点",
                "core_purpose": "无",
                "location": "无",
                "trigger_conditions": "无",
                "interaction_details": "无"
            }
        ])
        analysis_result.setdefault("function_design_strategies", [
            {
                "strategy_name": "未提取到策略",
                "function_point_relation": "无",
                "design_details": "无",
                "user_value": "无",
                "technical_implementation": "无",
                "applicable_scenarios": "无",
                "edge_case_handling": "无"
            }
        ])

        return analysis_result

    except Exception as e:
        return {"error": f"分析异常: {str(e)}"}


# 12. 路由：首页（上传+结果展示）
@app.route('/')
def index():
    return render_template("upload_v2.html")


# 13. 路由：上传与分析（支持500MB大视频）
@app.route('/analyze_function', methods=['POST'])
def analyze_function():
    # 验证文件
    if 'video' not in request.files:
        return jsonify({"error": "未上传视频文件"}), 400
    video_file = request.files['video']
    if not video_file.filename:
        return jsonify({"error": "未选择视频文件"}), 400

    # 保存文件（分块写入，避免内存溢出）
    try:
        video_ext = os.path.splitext(video_file.filename)[1].lower()
        if video_ext not in ['.mp4', '.avi', '.mov', '.mkv', '.flv', '.wmv']:
            return jsonify({"error": "仅支持MP4/AVI/MOV/MKV/FLV/WMV格式"}), 400

        # 500MB大小校验
        file_size_mb = video_file.content_length / 1024 / 1024
        if file_size_mb > 500:
            return jsonify({
                "error": f"视频过大（{round(file_size_mb, 2)}MB），最大支持500MB",
                "tips": "建议用HandBrake压缩码率或截取3-5分钟核心片段"
            }), 400

        # 创建数据库记录
        record = AnalysisRecord(
            filename=video_file.filename,
            file_size=file_size_mb,
            status='processing'
        )
        db.session.add(record)
        db.session.commit()
        record_id = record.id

        # 分块保存视频
        video_filename = f"video_{record_id}{video_ext}"
        video_path = os.path.join(app.config['UPLOAD_FOLDER'], video_filename)
        with open(video_path, 'wb') as f:
            chunk_size = 1024 * 1024  # 1MB分块
            while True:
                chunk = video_file.read(chunk_size)
                if not chunk:
                    break
                f.write(chunk)
        logger.info(f"保存视频: {video_path}（{round(file_size_mb, 2)}MB）")

    except Exception as e:
        return jsonify({"error": f"保存视频失败: {str(e)}", "tips": "检查磁盘空间是否充足"}), 500

    # 提取帧+分析
    try:
        function_frames = extract_function_frames(video_path)
        if not function_frames:
            record = AnalysisRecord.query.get(record_id)
            record.status = 'failed'
            db.session.commit()
            return jsonify({"error": "未提取到功能帧，请上传含产品操作的视频"}), 400

        # 分析设计思维
        analysis_result = analyze_design_thinking(function_frames)
        if "error" in analysis_result:
            record = AnalysisRecord.query.get(record_id)
            record.status = 'failed'
            db.session.commit()
            return jsonify(analysis_result), 400

        # 更新数据库记录
        record = AnalysisRecord.query.get(record_id)
        record.status = 'completed'
        record.result_data = json.dumps(analysis_result, ensure_ascii=False)
        db.session.commit()

        # 组装结果（含帧映射）
        frame_mapping = {str(f['time']): f['frame_filename'] for f in function_frames}
        final_result = {
            "analysis_id": record_id,
            "frame_mapping": frame_mapping,
            **analysis_result
        }
        return jsonify(final_result)

    except Exception as e:
        record = AnalysisRecord.query.get(record_id)
        record.status = 'failed'
        db.session.commit()
        return jsonify({"error": f"分析失败: {str(e)}"}), 500
    finally:
        # 清理临时视频
        if os.path.exists(video_path):
            try:
                os.remove(video_path)
                logger.info(f"清理临时视频: {video_path}")
            except Exception as e:
                logger.warning(f"清理视频失败: {str(e)}")


# 14. 路由：获取功能帧（前端预览）
@app.route('/get_frame/<filename>')
def get_frame(filename):
    frame_path = os.path.join(app.config['FRAME_FOLDER'], filename)
    if not os.path.exists(frame_path):
        return jsonify({"error": "帧文件不存在"}), 404
    return send_from_directory(app.config['FRAME_FOLDER'], filename)


# 15. 路由：导出Excel分析报告
@app.route('/export_analysis/<analysis_id>')
def export_analysis(analysis_id):
    try:
        # 获取分析结果
        record = AnalysisRecord.query.get(analysis_id)
        if not record or record.status != 'completed' or not record.result_data:
            return jsonify({"error": "无有效分析结果可导出"}), 400
        analysis_data = json.loads(record.result_data)

        # 生成Excel（3个sheet）
        buffer = io.BytesIO()
        with pd.ExcelWriter(buffer, engine='openpyxl') as writer:
            # Sheet1：设计思维总览
            overview_data = [
                ["产品定位", analysis_data['design_thinking_framework']['product_positioning']],
                ["目标用户", analysis_data['design_thinking_framework']['target_users']],
                ["问题拆解", analysis_data['design_thinking_framework']['user_problem_decomposition']],
                ["场景优先级", analysis_data['design_thinking_framework']['scene_priority_logic']],
                ["核心学习点", analysis_data['key_lessons']]
            ]
            pd.DataFrame(overview_data, columns=['维度', '内容']).to_excel(writer, sheet_name='设计思维总览',
                                                                           index=False)

            # Sheet2：具体设计内容
            content_data = [
                ["模块结构", analysis_data['concrete_design_content']['function_module_structure']],
                ["交互流程", analysis_data['concrete_design_content']['interaction_design_flow']],
                ["数据流转", analysis_data['concrete_design_content']['data_flow_design']]
            ]
            pd.DataFrame(content_data, columns=['设计维度', '具体内容']).to_excel(writer, sheet_name='具体设计内容',
                                                                                  index=False)

            # Sheet3：可复用策略
            strategy_data = []
            for strategy in analysis_data['reusable_design_strategies']:
                strategy_data.append([
                    strategy['strategy_name'],
                    strategy['concrete_content'],
                    strategy['applicable_scenario']
                ])
            pd.DataFrame(strategy_data, columns=['策略名称', '具体内容', '适用场景']).to_excel(writer,
                                                                                               sheet_name='可复用策略',
                                                                                               index=False)

        # 返回Excel
        buffer.seek(0)
        response = make_response(buffer.getvalue())
        response.headers['Content-Type'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
        response.headers['Content-Disposition'] = f'attachment; filename="产品设计思维分析_{analysis_id}.xlsx"'
        return response

    except Exception as e:
        logger.error(f"导出失败: {str(e)}")
        return jsonify({"error": f"导出失败: {str(e)}"}), 500


# 16. 路由：历史记录
@app.route('/history')
def history():
    records = AnalysisRecord.query.order_by(AnalysisRecord.upload_time.desc()).all()
    return render_template("history.html", records=[r.to_dict() for r in records])


# 17. 路由：查看历史分析结果
@app.route('/analysis/<analysis_id>')
def view_analysis(analysis_id):
    record = AnalysisRecord.query.get(analysis_id)
    if not record or record.status != 'completed' or not record.result_data:
        return "无有效分析结果", 404
    analysis_data = json.loads(record.result_data)
    # 构建帧映射（从历史帧文件中匹配，实际项目需优化存储）
    frame_mapping = {}
    for frame_file in os.listdir(app.config['FRAME_FOLDER']):
        if frame_file.startswith('frame_'):
            # 简化匹配：实际项目需存储帧与分析ID的关联
            frame_mapping[frame_file.split('_')[1][:6]] = frame_file
    return render_template("upload_v2.html", preload_data={
        "analysis_id": analysis_id,
        "frame_mapping": frame_mapping, **analysis_data
    })


# 18. 程序入口
if __name__ == '__main__':
    # 清理7天前的帧文件
    for frame_file in os.listdir(app.config['FRAME_FOLDER']):
        frame_path = os.path.join(app.config['FRAME_FOLDER'], frame_file)
        if os.path.getmtime(frame_path) < time.time() - 7 * 24 * 3600:
            os.remove(frame_path)

    # 启动服务（生产环境建议使用Gunicorn等WSGI服务器）
    app.run(host='0.0.0.0', port=5000, debug=True)